package vg_connector.implementation;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import vg_connector.interfaces.AnswerAdapter;
import vg_connector.interfaces.IServerAPIService;
import vg_connector.interfaces.IServerListener;
import vg_connector.interfaces.actions.ParentAnswer;
import vg_connector.interfaces.actions.ParentMessage;
import vg_connector.interfaces.actions.PingAction;
import vg_connector.interfaces.exceptions.ConnectException;

import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * @author Timur Zolotuhin (tzolotuhin@parallels.com)
 */
public class ServerAPIService implements IServerAPIService {
    // Main data
    private List<IServerListener> serverListeners = Lists.newArrayList();
    private ChannelWorker channelWorker;
    private ChannelListener innerChannelListener;
    private Map<UUID, AnswerAdapter> answerListeners;

    // Mutex
    private final Object generalMutex = new Object();

    public ServerAPIService() {
        innerChannelListener = new ChannelListener() {
            @Override
            public void connect(UUID id, int type) {
                if (type == PingAction.CLIENT_OWNER_ROLE) {
                    doNotifyConnectListeners(syncCopyServerListeners());
                }
            }

            @Override
            public void disconnect(UUID id) {
                doNotifyDisconnectListeners(syncCopyServerListeners());
            }

            @Override
            public void receive(ParentMessage message) {
                doNotifyReceiveListeners(syncCopyServerListeners(), message);
            }
        };
    }

    @Override
    public void connect(UUID instanceId) throws ConnectException {
        synchronized (generalMutex) {
            try {
                if (channelWorker != null && channelWorker.isConnect())
                    disconnect();

                channelWorker = new ChannelWorker(instanceId, PingAction.SERVER_OWNER_ROLE);
                channelWorker.addChannelListener(innerChannelListener);
                channelWorker.connect();

                answerListeners = Maps.newHashMap();
            } catch (Throwable ex) {
                throw new ConnectException(ex);
            }
        }
    }

    @Override
    public void disconnect() throws ConnectException {
        synchronized (generalMutex) {
            channelWorker.disconnect();
            channelWorker.removeChannelListener(innerChannelListener);
        }
    }

    @Override
    public void sendAnswer(ParentAnswer answer) throws ConnectException {
        ChannelWorker tmpChannelWorker;
        synchronized (generalMutex) {
            tmpChannelWorker = channelWorker;
        }

        try {
            tmpChannelWorker.sendMessage(answer);
        } catch (Throwable ex) {
            throw new ConnectException(ex);
        }
    }

    @Override
    public void addServerListener(IServerListener serverListener) {
        synchronized (generalMutex) {
            serverListeners.add(serverListener);
        }
    }

//==============================================================================
//------------------PRIVATE METHODS---------------------------------------------
    private List<IServerListener> syncCopyServerListeners() {
        List<IServerListener> copyListeners;

        synchronized (generalMutex) {
            copyListeners = Lists.newArrayList(serverListeners);
        }

        return copyListeners;
    }

    private void doNotifyConnectListeners(List<IServerListener> serverListeners) {
        for (IServerListener serverListener : serverListeners) {
            serverListener.connect();
        }
    }

    private void doNotifyDisconnectListeners(List<IServerListener> serverListeners) {
        for (IServerListener serverListener : serverListeners) {
            serverListener.disconnect();
        }
    }

    private void doNotifyReceiveListeners(List<IServerListener> serverListeners, ParentMessage message) {
        for (IServerListener serverListener : serverListeners) {
            serverListener.receive(message);
        }
    }
}
